#include "logs.hpp"

#define CUBEFS_SOURCE "logs.cpp:" CUBEFS_AT

jmethodID logBeginMID       = NULL;
jmethodID logEndMID         = NULL;

jmethodID logBoolMID        = NULL;
jmethodID logByteMID        = NULL;
jmethodID logUByteMID       = NULL;
jmethodID logShortMID       = NULL;
jmethodID logUShortMID      = NULL;
jmethodID logIntMID         = NULL;
jmethodID logUIntMID        = NULL;
jmethodID logBigIntMID      = NULL;
jmethodID logFloatMID       = NULL;
jmethodID logDoubleMID      = NULL;
jmethodID logIDValueMID     = NULL;
jmethodID logStringRefMID   = NULL;
jmethodID logConstCharMID   = NULL;
jmethodID logVoidPtrMID     = NULL;


class JavaLogHandler: public cubefs::api::LogHandler {

    JNIEnv *env;
    jclass clazz_;
    jclass idValueClazz_;

    bool locked_;

public:

    JavaLogHandler(JNIEnv *env0, jclass clazz, jclass idValueClazz): env(env0), locked_(false) {
        clazz_ = (jclass)env->NewGlobalRef(clazz);
        CUBEFS_EXC_ASSERT(clazz_!=NULL);

        idValueClazz_ = (jclass)env->NewGlobalRef(idValueClazz);
        CUBEFS_EXC_ASSERT(idValueClazz_!=NULL);
    }

    virtual ~JavaLogHandler() throw () {
        try {
            env->DeleteGlobalRef(clazz_);
            env->DeleteGlobalRef(idValueClazz_);
        }
        catch (...) {}
    }

    jobject object() {
        if (GetPeer() == 0) {
            throw NullPointerException(CUBEFS_SOURCE, "No Java peer for log handler");
        }

        return (jobject)GetPeer();
    }

    virtual void begin(Int level) {
        jobject obj = object();
        if (env->MonitorEnter(obj) == JNI_OK) {
            locked_ = true;
        }
        else {
            locked_ = false;
            CUBEFS_EXC_TEST1("Can't lock on monitor in native JavaLogHandler.begin()");
        }

        env->CallVoidMethod(obj, logBeginMID, level);

        if (env->ExceptionCheck()) {
            env->MonitorExit(obj);
            throw JavaException(CUBEFS_SOURCE, "Exception in Java code for JavaLogHandler.begin()");
        }
    }

    virtual void end() {
        jobject obj = object();
        
        env->CallVoidMethod(obj, logEndMID);

        if (locked_) {
            env->MonitorExit(obj);
        }

        env->ExceptionClear();

        locked_ = false;
    }

    virtual void log(bool value) {
        env->CallVoidMethod(object(), logBoolMID, value);
    }

    virtual void log(Byte value) {
        env->CallVoidMethod(object(), logByteMID, value);
    }

    virtual void log(UByte value) {
        env->CallVoidMethod(object(), logUByteMID, value);
    }

    virtual void log(Short value) {
        env->CallVoidMethod(object(), logShortMID, value);
    }

    virtual void log(UShort value) {
        env->CallVoidMethod(object(), logUShortMID, value);
    }

    virtual void log(Int value) {
        env->CallVoidMethod(object(), logIntMID, value);
    }

    virtual void log(UInt value) {
        env->CallVoidMethod(object(), logUIntMID, value);
    }

    virtual void log(BigInt value) {
        env->CallVoidMethod(object(), logBigIntMID, value);
    }

    virtual void log(float value) {
        env->CallVoidMethod(object(), logFloatMID, value);
    }

    virtual void log(double value) {
        env->CallVoidMethod(object(), logDoubleMID, value);
    }

    virtual void log(IDValue value) {
        TypedIDValue* id = new TypedIDValue(value);
        long handle = Ptr2Handle(id);
        env->CallVoidMethod(object(), logIDValueMID, handle);
    }

    virtual void log(StringRef value) {
        jstring str = env->NewStringUTF(value.c_str());
        env->CallVoidMethod(object(), logStringRefMID, str);
    }
    
    virtual void log(const char* value) {
        jstring str = env->NewStringUTF(value);
        env->CallVoidMethod(object(), logStringRefMID, str);
    }
    
    virtual void log(const void* value) {
        env->CallVoidMethod(object(), logVoidPtrMID, (BigInt)value);
    }

    virtual Type* type() const {
        return type_;
    }

    static Type* MyType() {
        return type_;
    }

    static void Init();

private:
    static Type* type_;
};


JNIEXPORT jlong JNICALL METADATA3(AbstractJavaLogHandler, createNew) (JNIEnv *env, jclass clazz, jclass idValueClazz)
{
    CUBEFS_EXC_BEGIN

    return Ptr2Handle(new JavaLogHandler(env, clazz, idValueClazz));

    CUBEFS_EXC_HANDLE
    return 0;
}


cubefs::api::Type* JavaLogHandler::type_ = NULL;

void JavaLogHandler::Init() {
    AbstractType<> *type = new AbstractType<>("cubefs::api::models::JavaLogHandler", LogHandler::MyType());
    type_ = type;
}

#define DEFINE_MID(name, javaName, signature)                                   \
    log##name##MID = env->GetMethodID(handlerClass, javaName, signature);       \
    if (log##name##MID == NULL) return


void InitLogHandler(JNIEnv *env, jclass handlerClass) {
    JavaLogHandler::Init();

    DEFINE_MID(Begin,   "begin",    "(I)V");
    DEFINE_MID(End,     "end",      "()V");

    DEFINE_MID(Bool,    "log",      "(Z)V");
    DEFINE_MID(Byte,    "log",      "(B)V");
    DEFINE_MID(UByte,   "log",      "(S)V");
    DEFINE_MID(Short,   "log",      "(S)V");
    DEFINE_MID(UShort,  "log",      "(I)V");
    DEFINE_MID(Int,     "log",      "(I)V");
    DEFINE_MID(UInt,    "log",      "(J)V");
    DEFINE_MID(BigInt,  "log",      "(J)V");

    DEFINE_MID(Float,   "log",      "(F)V");
    DEFINE_MID(Double,  "log",      "(D)V");

    DEFINE_MID(IDValue, "logId",    "(J)V");
    
    DEFINE_MID(StringRef,"log",     "(Ljava/lang/String;)V");
    DEFINE_MID(ConstChar,"log",     "(Ljava/lang/String;)V");
    DEFINE_MID(VoidPtr,  "logPtr",  "(J)V");
}

#undef DEFINE_MID

void CleanLogHandler(JNIEnv *evn) {
    
}

#undef CUBEFS_SOURCE